﻿using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public class MultiThreadFileCopier
{
    private string _sourceDirectory;
    private string _targetDirectory;
    private int _maxThreads;
    private long _totalBytes;
    private long _copiedBytes;
    private int _totalFiles;
    private int _copiedFiles;
    private ConcurrentQueue<string> _fileQueue;
    private CancellationTokenSource _cancellationTokenSource;

    // 进度报告事件
    public event Action<ProgressInfo> ProgressChanged;

    public MultiThreadFileCopier(string sourceDirectory, string targetDirectory, int maxThreads = 4)
    {
        _sourceDirectory = sourceDirectory ?? throw new ArgumentNullException(nameof(sourceDirectory));
        _targetDirectory = targetDirectory ?? throw new ArgumentNullException(nameof(targetDirectory));
        _maxThreads = Math.Max(1, maxThreads);
        _fileQueue = new ConcurrentQueue<string>();
        _cancellationTokenSource = new CancellationTokenSource();
    }

    public class ProgressInfo
    {
        public int TotalFiles { get; set; }
        public int CopiedFiles { get; set; }
        public long TotalBytes { get; set; }
        public long CopiedBytes { get; set; }
        public double ProgressPercentage => TotalBytes > 0 ? (double)CopiedBytes / TotalBytes * 100 : 0;
        public string CurrentFile { get; set; }
        public bool IsCompleted { get; set; }
    }

    /// <summary>
    /// 开始拷贝文件
    /// </summary>
    public async Task<bool> StartCopyAsync()
    {
        try
        {
            // 验证源目录
            if (!Directory.Exists(_sourceDirectory))
            {
                throw new DirectoryNotFoundException($"源目录不存在: {_sourceDirectory}");
            }

            // 创建目标目录
            Directory.CreateDirectory(_targetDirectory);

            // 扫描所有文件
            Console.WriteLine("正在扫描文件...");
            var files = Directory.GetFiles(_sourceDirectory, "*.*", SearchOption.AllDirectories);
            _totalFiles = files.Length;

            // 计算总文件大小并构建文件队列
            foreach (var file in files)
            {
                var fileInfo = new FileInfo(file);
                _totalBytes += fileInfo.Length;
                _fileQueue.Enqueue(file);
            }

            Console.WriteLine($"找到 {_totalFiles} 个文件，总大小: {FormatFileSize(_totalBytes)}");

            // 启动多个拷贝任务
            var tasks = new Task[_maxThreads];
            for (int i = 0; i < _maxThreads; i++)
            {
                tasks[i] = Task.Run(() => CopyFilesWorker(_cancellationTokenSource.Token));
            }

            // 启动进度报告任务
            var progressTask = Task.Run(ReportProgress);

            // 等待所有任务完成
            await Task.WhenAll(tasks);
            _cancellationTokenSource.Cancel();

            // 最终进度报告
            ReportFinalProgress();

            Console.WriteLine($"\n文件拷贝完成！共拷贝 {_copiedFiles} 个文件，总大小: {FormatFileSize(_copiedBytes)}");
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"拷贝过程中发生错误: {ex.Message}");
            return false;
        }
    }

    /// <summary>
    /// 停止拷贝
    /// </summary>
    public void StopCopy()
    {
        _cancellationTokenSource.Cancel();
        Console.WriteLine("拷贝操作已停止");
    }

    /// <summary>
    /// 文件拷贝工作线程
    /// </summary>
    private async Task CopyFilesWorker(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested && _fileQueue.TryDequeue(out string filePath))
        {
            try
            {
                await CopySingleFileAsync(filePath, cancellationToken);
            }
            catch (OperationCanceledException)
            {
                // 操作被取消，正常退出
                break;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"拷贝文件失败 {filePath}: {ex.Message}");
            }
        }
    }

    /// <summary>
    /// 拷贝单个文件
    /// </summary>
    private async Task CopySingleFileAsync(string sourceFilePath, CancellationToken cancellationToken)
    {
        var fileInfo = new FileInfo(sourceFilePath);
        string relativePath = GetRelativePath(sourceFilePath, _sourceDirectory);
        string targetFilePath = Path.Combine(_targetDirectory, relativePath);

        // 创建目标目录
        Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath));

        // 报告当前正在拷贝的文件
        OnProgressChanged(new ProgressInfo
        {
            CurrentFile = Path.GetFileName(sourceFilePath),
            TotalFiles = _totalFiles,
            CopiedFiles = _copiedFiles,
            TotalBytes = _totalBytes,
            CopiedBytes = Interlocked.Read(ref _copiedBytes)
        });

        // 使用文件流进行拷贝，支持大文件
        const int bufferSize = 81920; // 80KB 缓冲区
        byte[] buffer = new byte[bufferSize];

        using (var sourceStream = new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true))
        using (var targetStream = new FileStream(targetFilePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, true))
        {
            int bytesRead;
            while ((bytesRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
            {
                await targetStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);

                // 更新已拷贝字节数
                Interlocked.Add(ref _copiedBytes, bytesRead);

                // 定期报告进度
                if (bytesRead == bufferSize) // 只在完整读取时报告，减少开销
                {
                    OnProgressChanged(new ProgressInfo
                    {
                        CurrentFile = Path.GetFileName(sourceFilePath),
                        TotalFiles = _totalFiles,
                        CopiedFiles = _copiedFiles,
                        TotalBytes = _totalBytes,
                        CopiedBytes = Interlocked.Read(ref _copiedBytes)
                    });
                }

                cancellationToken.ThrowIfCancellationRequested();
            }
        }

        // 保持文件属性
        File.SetCreationTime(targetFilePath, fileInfo.CreationTime);
        File.SetLastWriteTime(targetFilePath, fileInfo.LastWriteTime);
        File.SetAttributes(targetFilePath, fileInfo.Attributes);

        // 更新已拷贝文件计数
        Interlocked.Increment(ref _copiedFiles);

        Console.WriteLine($"已拷贝: {relativePath} ({FormatFileSize(fileInfo.Length)})");
    }

    /// <summary>
    /// 报告进度
    /// </summary>
    private async Task ReportProgress()
    {
        while (!_cancellationTokenSource.Token.IsCancellationRequested)
        {
            OnProgressChanged(new ProgressInfo
            {
                CurrentFile = "处理中...",
                TotalFiles = _totalFiles,
                CopiedFiles = _copiedFiles,
                TotalBytes = _totalBytes,
                CopiedBytes = Interlocked.Read(ref _copiedBytes)
            });

            await Task.Delay(500); // 每500ms报告一次进度
        }
    }

    /// <summary>
    /// 报告最终进度
    /// </summary>
    private void ReportFinalProgress()
    {
        OnProgressChanged(new ProgressInfo
        {
            CurrentFile = "完成",
            TotalFiles = _totalFiles,
            CopiedFiles = _copiedFiles,
            TotalBytes = _totalBytes,
            CopiedBytes = _copiedBytes,
            IsCompleted = true
        });
    }

    /// <summary>
    /// 触发进度改变事件
    /// </summary>
    private void OnProgressChanged(ProgressInfo progressInfo)
    {
        ProgressChanged?.Invoke(progressInfo);
    }

    /// <summary>
    /// 获取相对路径
    /// </summary>
    private string GetRelativePath(string fullPath, string basePath)
    {
        if (!basePath.EndsWith(Path.DirectorySeparatorChar.ToString()))
            basePath += Path.DirectorySeparatorChar;

        Uri fullUri = new Uri(fullPath);
        Uri baseUri = new Uri(basePath);

        return Uri.UnescapeDataString(baseUri.MakeRelativeUri(fullUri).ToString().Replace('/', Path.DirectorySeparatorChar));
    }

    /// <summary>
    /// 格式化文件大小
    /// </summary>
    private string FormatFileSize(long bytes)
    {
        string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
        int counter = 0;
        decimal number = bytes;

        while (Math.Round(number / 1024) >= 1)
        {
            number /= 1024;
            counter++;
        }

        return $"{number:n2} {suffixes[counter]}";
    }
}